#pragma once
#include<iostream>
#include<string>
#include<mutex>
#include<filesystem>
#include <fstream>
#include<ctime>
#include<cstdio>
#include<unistd.h>
#include<sstream>
#include<memory>


class LogStrategy
{
public:
    virtual void FlushStrategy(const std::string& message) = 0;
};


class ConsoleLogStrategy : public LogStrategy
{
public:
    void FlushStrategy(const std::string& message)  override
    {
        std::lock_guard<std::mutex> lg(_mutex); 
        std::cout << message << std::endl;
    }

private:
    std::mutex _mutex;
};

class FileLogStrategy : public LogStrategy
{
public:
    FileLogStrategy(const std::string& path = "./log",const std::string& name = "my.log")
    :_path(path)
    ,_name(name)
    {
        std::lock_guard<std::mutex> lg(_mutex);
        if(std::filesystem::exists(_path))
        {
            return;
        }
        //不存在指定目录,创建目录
        try
        {
            std::filesystem::create_directories(_path);
        }
        catch(const std::filesystem::filesystem_error& e)
        {
            std::cerr << e.what() <<std::endl;
        }
    }

    void FlushStrategy(const std::string& message) override
    {
        std::string pathname  = _path +  (_path.back() == '/' ? "": "/") + _name;
        std::lock_guard<std::mutex> lg(_mutex);
        std::ofstream out(pathname.c_str(),std::ofstream::app);
        if(!out.is_open())
        {
            return;
        }
        out << message <<std::endl;
        out.close();
    }

private:
    std::string _path;
    std::string _name;
    std::mutex _mutex;
};

enum class LogLevel
{
    DEBUG,
    INFO,
    WARNING,
    ERROR,
    FATAL
};

std::string GetTime()
{
    time_t timestamp  = time(nullptr);
    struct tm curtime;
    localtime_r(&timestamp,&curtime);
    char buffer[128];
    snprintf(buffer,sizeof(buffer),"%4d-%02d-%02d %02d:%02d:%02d",curtime.tm_year+1900,curtime.tm_mon+1,curtime.tm_mday,curtime.tm_hour,curtime.tm_min,curtime.tm_sec);
    return buffer;
}

std::string LevelToString(LogLevel level)
{
    switch(level)
    {   
        case LogLevel::DEBUG:
        return "DEBUG";
        case LogLevel::INFO:
        return "INFO";
        case LogLevel::WARNING:
        return "WARNING";
        case LogLevel::ERROR:
        return "ERROR";
        case LogLevel::FATAL:
        return "FATAL";
        default:
        return "UNKNOWN";
    }
}

class Log
{
public:
    Log()
    {
        FlushConsole();
    }

    void FlushConsole()
    {
        _ls = std::make_unique<ConsoleLogStrategy>();
    }

    void FlushFile()
    {
        _ls = std::make_unique<FileLogStrategy>();
    }

    class LogMessage
    {
    public:
        LogMessage(LogLevel level,const std::string& filename,int line,Log& log)
        :_curtime(GetTime())
        ,_level(level)
        ,_filename(filename)
        ,_line(line)
        ,_pid(getpid())
        ,_log(log)
        {
            std::stringstream ss;
            ss << "[" << _curtime << "] "
            << "[" << LevelToString(_level) << "] "
            << "[" << _pid << "] "
            << "[" << _filename <<"] "
            << "[" << _line << "] - ";
            _info += ss.str();
        }

        ~LogMessage()
        {
            if(_log._ls)
            {
                _log._ls->FlushStrategy(_info);
            }
        }

        template <typename T>
        LogMessage& operator<<(const T& data)
        {
            std::stringstream ss;
            ss << data;
            _info += ss.str();           
            return *this;
        }

    private:
        std::string _curtime;
        LogLevel _level;
        std::string _filename;
        int _line;
        pid_t _pid;
        std::string _info;
        Log& _log;
    };

    LogMessage operator()(LogLevel level,const std::string& filename,int line)
    {
        return LogMessage(level,filename,line,*this);
    }

private:
    std::unique_ptr<LogStrategy> _ls;
};

Log mylog;

#define LOG(level) mylog(level,__FILE__,__LINE__)

